July 14, 2020
By: anna

HLS(http live streaming) 的移动端播放

这是一个移动端的直播项目,还有在线互动功能。

首先视频我们想到的都是使用video标签,但是ios虽然可以使用,但是安卓不行,所以在这里,我们需要对移动端进行判断,ios/andriod分别使用不同的组件。

先来对移动端进行判断


(def ios? (->> (.-userAgent js/navigator)
              (re-seq #"iPhone")
              (count)
              zero?
              not))

这里我们只需要对ios进行判断就可以了,浏览器的用户代理(.-userAgent js/navigator)。

ios/andriod判断成功,那我们看看ios和andriod都用什么组件实现视频功能。


(if ios?
        [:video
         {:width "100%"
          :poster "/imgs/zhanweitu.jpg"
          :controls "controls"
          :src "http://xhlive.3vyd.com/live/007.m3u8"}]
        [:> Player {:playsInline true
                    :poster "/imgs/zhanweitu.jpg"
                    :src "http://xhlive.3vyd.com/live/007.m3u8"}
         ])

上面说了,ios可以直接使用video,这里安卓用的player(["video-react" :refer [Player]]这是组件在clojure中的调用方法)。不过这个组件同样在ios上不能进行视频播放。

有兴趣的小伙伴可以看看video.js和video-react,很有趣。

这里遇到过一个坑,在if外面套了两层的box(["@material-ui/core/Box" :default Box],项目结构使用的material-ui框架),播放的视频要是比较大,会把下层的兄弟给覆盖掉,限制高度也不行,还是会被覆盖掉,所以写的时候还是要注意,尽量不要留多余的代码,保持良好的习惯,这种bug是绝对不应该有的!!!(反思)

下面的互动聊天,用到了MessageList(["react-chat-elements" :refer [MessageBox MessageList]]),显示聊天记录。


(defn msg->data [msgs]
  (mapv (fn [value]
          {:position "right"
           :type "text"
           :text (:msg value)
           :date (:time value)})
        msgs))

[:> MessageList
      {:className "message-list"
       :lockable true
       :toBottomHeight "100%"
       :dataSource (msg->data @(rf/subscribe [:all-msg]))}]

聊天内容和发送的内容都是使用的websocket,关于这个以前有一篇专门的博客。

还有MessageList的使用,刚开始写的时候没有限定高度,所以滚动条是在一整个页面滚动的,这样发送数据的时候,发现最新的一条没有展示出来。还需要手动滚动才行,这样的体验很不好。所以在这里height要写出来,bug就自然解决了。

在这里再展示一下大牛的写法:


(def theme (createMuiTheme #js {:palette}) )

(def useStyles
  (makeStyles
   (fn [theme]
     (clj->js {:root {:padding "2px 4px",:display "flex",:alignItems "center",:width 400,},
               :input {:marginLeft (.spacing theme 1) :flex 1,},
               :iconButton {:padding 10,},
               :divider {:height 28,:margin 4,}}))))


(let [classes (useStyles) ]
     [:> Box {:align-self :flex-end
              :width "100%"}
      [:> Paper {:component "form"
                 :id "box-input"
                 :class-name (.-root classes)}
       [:> InputBase {:placeholder "请输入您的发言"
                      :id "messge-input"
                      :on-change (fn [event ]
                                   (reset! current-msg (.. event -target -value) ))
                      :class-name (.-input classes)}]

       [:> Divider {:orientation "vertical"
                    :class-name (.-divider classes)}]
       [:> Button {:color "primary"
                   :aria-label "directions"
                   :onClick (fn []
                              (prn "发出新消息...")
                              (when-not (empty? @current-msg)
                                (client/chsk-send! [:user/new-msg @current-msg])
                                (set! (.-value (.getElementById js/document "messge-input")) "")
                                (reset! current-msg "")))}
        "提交"
        [:> DirectionsIcon]]]])

这是发送内容的部分

下面是websocket的数据传输及数据存储


(timbre/set-level! :debug)

(defn ->output! [fmt & args]
  (let [msg (apply encore/format fmt args)]
    (timbre/info msg)))

(let [ ;; For this example, select a random protocol:
      rand-chsk-type :ws
      _ (->output! "Randomly selected chsk type: %s" rand-chsk-type)

      ;; Serializtion format, must use same val for client + server:
      packer :edn        ; Default packer, a good choice in most cases
      ;; (sente-transit/get-transit-packer) ; Needs Transit dep
      {:keys [chsk ch-recv send-fn state]}
      (sente/make-channel-socket-client!
       "/chsk"                    ; Must match server Ring routing URL
       ""
       {:protocol :https
        :host "xhliveactivity.3vyd.com"
        :port 443
        :type   rand-chsk-type
        :packer packer})]

  (def chsk       chsk)
  (def ch-chsk    ch-recv)           ; ChannelSocket's receive channel
  (def chsk-send! send-fn)           ; ChannelSocket's send API fn
  (def chsk-state state)             ; Watchable, read-only atom
  )



(defmulti -event-msg-handler  "Multimethod to handle Sente `event-msg`s"
  :id                                   ; Dispatch on event-id
  )

(defn event-msg-handler
  "Wraps `-event-msg-handler` with logging, error catching, etc."
  [{:as ev-msg :keys [id ?data event]}]
  (-event-msg-handler ev-msg))

(defmethod -event-msg-handler
  :default ; Default/fallback case (no other matching handler)
  [{:as ev-msg :keys [event]}]
  (->output! "Unhandled event: %s" event))

(kf/reg-event-db
 :init-msg
 (fn [db [event]]
   (prn "init......db with msg" event)
   (assoc db :msgs event)))

(kf/reg-event-db
 :init-users
 (fn [db [event]]
   (prn "init......db with users")
   (assoc db :users event)))

(rf/reg-sub
 :all-msg
 (fn [db]
   (:msgs db)))

(rf/reg-event-db
 :add-msg
 (fn [db [_ event]]
   (prn "event:" event)
   (assoc db :msgs
          (conj (:msgs db )
                event))))

(rf/reg-event-db
 :modify-users
 (fn [db [_ count]]
   (prn "event:modify-users" count)
   (assoc db :users count)))


(defn give-me-all []
  (chsk-send! [:user/get-all-msgs nil]
              1000
              (fn [cb-reply]
                (when (cb-success? cb-reply)
                  (if cb-reply
                    (do
                      (rf/dispatch [:init-msg cb-reply])
                      (prn "success:" cb-reply))
                    (do
                      (prn "failed:" cb-reply)
                      ))))))

(defn give-init-users []
  (chsk-send! [:user/get-users nil]
              1000
              (fn [cb-reply]
                (when (cb-success? cb-reply)
                  (if cb-reply
                    (do
                      (rf/dispatch [:init-users cb-reply])
                      (prn "success:" cb-reply))
                    (prn "failed:" cb-reply)
                    )))))


(defmethod -event-msg-handler :chsk/state
  [{:as ev-msg :keys [?data]}]
  (let [[old-state-map new-state-map] (have vector? ?data)]
    (if (:first-open? new-state-map)
      (do
        (prn "........................")
        (->output! "Channel socket successfully established!: %s" new-state-map)
        (give-me-all)
        (give-init-users))
      (->output! "Channel socket state change: %s"              new-state-map))))

(defmethod -event-msg-handler :chsk/recv
  [{:as ev-msg :keys [?data]}]
  (->output! "Push event from server: %s" ?data)
  (let [[event-id event-body] ?data]
    (prn "=======" event-id)
    (case event-id
      :msg/broad-cast (rf/dispatch [:add-msg event-body])
      :msg/modify-users (rf/dispatch [:modify-users event-body]))))

(defmethod -event-msg-handler :chsk/handshake
  [{:as ev-msg :keys [?data]}]
  (let [[?uid ?csrf-token ?handshake-data] ?data]
    (->output! "Handshake: %s" ?data)))


;; :msg/broad-cast

(defmethod -event-msg-handler :msg/broad-cast
  [{:as ev-msg :keys [?data]}]
  (let [[?uid ?csrf-token ?handshake-data] ?data]
    (rf/dispatch [:add-msg ?data])))


(defonce router_ (atom nil))
(defn  stop-router! [] (when-let [stop-f @router_] (stop-f)))
(defn start-router! []
  (stop-router!)
  (reset! router_
          (sente/start-client-chsk-router!
           ch-chsk event-msg-handler)))

(defn start! [] (start-router!))

(defonce _start-once (start!))

(comment
  (a/go-loop []
    (println (a/<! ch-chsk))
    (recur))
  )



(rf/reg-fx :ws-event
           (fn [[msg-id event]]
             (chsk-send! [msg-id event])))

(rf/reg-fx :ws-event-cb
           (fn [[msg-id event success-id fail-id :as m] ]
             (prn "m:" m)
             (chsk-send! [msg-id event] 1000
                         (fn [cb-reply]
                           (when (cb-success? cb-reply)
                             (if cb-reply
                               (do
                                 (rf/dispatch [success-id cb-reply])
                                 (->output! "消息 %s 成功, 返回值:%s" m cb-reply))
                               (do
                                 (->output! "消息 %s 失败" m)
                                 (rf/dispatch [fail-id cb-reply]))))))))
Tags: clojure